---
title: "JPA @OneToMany 关系"
date: 2020-12-29
categories:
- java
- spring-jpa
tags:
---

<div id="content">
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org3e7af71">单向@OneToMany关联</a></li>
<li><a href="#orgf794b32">单向@OneToMany关联与@JoinColumn一起使用</a></li>
<li><a href="#org11d7ddd">双向关联</a></li>
<li><a href="#org86d279e">ManyToOne 单向关联</a></li>
</ul>
</div>
</div>
<blockquote>
<p>
<a href="https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/">https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/</a>
</p>
</blockquote>
<p>
使用 @OneToMany 能够很方便地进行简单的Join查询而不用编写join sql语句。
</p>
<p>
假设一个数据库关系：
</p>
<table border="2" cellpadding="6" cellspacing="0" frame="hsides" rules="groups">
<colgroup>
<col class="org-left"/>
<col class="org-left"/>
</colgroup>
<thead>
<tr>
<th class="org-left" scope="col">post</th>
<th class="org-left" scope="col"> </th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">id</td>
<td class="org-left">int</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">title</td>
<td class="org-left">varchar</td>
</tr>
</tbody>
</table>
<table border="2" cellpadding="6" cellspacing="0" frame="hsides" rules="groups">
<colgroup>
<col class="org-left"/>
<col class="org-left"/>
</colgroup>
<thead>
<tr>
<th class="org-left" scope="col">post_comment</th>
<th class="org-left" scope="col"> </th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">id</td>
<td class="org-left">id</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">review</td>
<td class="org-left">text</td>
</tr>
<tr>
<td class="org-left">post_id</td>
<td class="org-left">int</td>
</tr>
</tbody>
</table>
<div class="outline-2" id="outline-container-org3e7af71">
<h2 id="org3e7af71">单向@OneToMany关联</h2>
<div class="outline-text-2" id="text-org3e7af71">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@Entity</span>(name = <span style="font-style: italic;">"Post"</span>)
<span style="font-weight: bold; text-decoration: underline;">@Table</span>(name = <span style="font-style: italic;">"post"</span>)
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">Post</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Id</span>
    <span style="font-weight: bold; text-decoration: underline;">@GeneratedValue</span>
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">Long</span> <span style="font-weight: bold; font-style: italic;">id</span>;

    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">String</span> <span style="font-weight: bold; font-style: italic;">title</span>;

    <span style="font-weight: bold; text-decoration: underline;">@OneToMany</span>(
        cascade = <span style="font-weight: bold; text-decoration: underline;">CascadeType</span>.ALL,
        orphanRemoval = <span style="font-weight: bold; text-decoration: underline;">true</span>
    )
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">List</span>&lt;<span style="font-weight: bold; text-decoration: underline;">PostComment</span>&gt; <span style="font-weight: bold; font-style: italic;">comments</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">ArrayList</span>&lt;&gt;();

    <span style="font-weight: bold; font-style: italic;">//</span><span style="font-weight: bold; font-style: italic;">Constructors, getters and setters removed for brevity</span>
}

<span style="font-weight: bold; text-decoration: underline;">@Entity</span>(name = <span style="font-style: italic;">"PostComment"</span>)
<span style="font-weight: bold; text-decoration: underline;">@Table</span>(name = <span style="font-style: italic;">"post_comment"</span>)
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">PostComment</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Id</span>
    <span style="font-weight: bold; text-decoration: underline;">@GeneratedValue</span>
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">Long</span> <span style="font-weight: bold; font-style: italic;">id</span>;

    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">String</span> <span style="font-weight: bold; font-style: italic;">review</span>;

    <span style="font-weight: bold; font-style: italic;">//</span><span style="font-weight: bold; font-style: italic;">Constructors, getters and setters removed for brevity</span>
}
</pre>
</div>
<p>
创建Post并添加三个评论，
</p>
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">Post</span> <span style="font-weight: bold; font-style: italic;">post</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">Post</span>(<span style="font-style: italic;">"First post"</span>);

post.getComments().add(
    <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">PostComment</span>(<span style="font-style: italic;">"My first review"</span>)
);
post.getComments().add(
    <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">PostComment</span>(<span style="font-style: italic;">"My second review"</span>)
);
post.getComments().add(
    <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">PostComment</span>(<span style="font-style: italic;">"My third review"</span>)
);

entityManager.persist(post);
</pre>
</div>
<p>
Hibernate生成的语句是
</p>
<div class="org-src-container">
<pre class="src src-sql">
# post
<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post (title, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'First post'</span>, 1)

# post_comment
<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My first review'</span>, 2)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My second review'</span>, 3)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My third review'</span>, 4)

# Hibernate自动生成的中间表，即使是OneToMany，Hibernate也以多对多的方式存储了这些记录，这样效率肯定是比较低的
<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_post_comment (Post_id, comments_id)
<span style="font-weight: bold;">values</span> (1, 2)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_post_comment (Post_id, comments_id)
<span style="font-weight: bold;">values</span> (1, 3)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_post_comment (Post_id, comments_id)
<span style="font-weight: bold;">values</span> (1, 4)
</pre>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-orgf794b32">
<h2 id="orgf794b32">单向@OneToMany关联与@JoinColumn一起使用</h2>
<div class="outline-text-2" id="text-orgf794b32">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">修改Post.comments，添加@JoinColumn注解</span>
<span style="font-weight: bold; text-decoration: underline;">@OneToMany</span>(cascade = <span style="font-weight: bold; text-decoration: underline;">CascadeType</span>.ALL, orphanRemoval = <span style="font-weight: bold; text-decoration: underline;">true</span>)
<span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">post_id为PostComment中的postId列名，不是字段名</span>
<span style="font-weight: bold; text-decoration: underline;">@JoinColumn</span>(name = <span style="font-style: italic;">"post_id"</span>)
<span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">List</span>&lt;<span style="font-weight: bold; text-decoration: underline;">PostComment</span>&gt; <span style="font-weight: bold; font-style: italic;">comments</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">ArrayList</span>&lt;&gt;();
</pre>
</div>
<p>
执行上面的添加，生成的SQL语句：
</p>
<div class="org-src-container">
<pre class="src src-sql"><span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post (title, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'First post'</span>, 1)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My first review'</span>, 2)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My second review'</span>, 3)

<span style="font-weight: bold;">insert</span> <span style="font-weight: bold;">into</span> post_comment (review, id)
<span style="font-weight: bold;">values</span> (<span style="font-style: italic;">'My third review'</span>, 4)

# 生成的 <span style="font-weight: bold;">update</span>，
<span style="font-weight: bold;">update</span> post_comment <span style="font-weight: bold;">set</span> post_id = 1 <span style="font-weight: bold;">where</span> id = 2

<span style="font-weight: bold;">update</span> post_comment <span style="font-weight: bold;">set</span> post_id = 1 <span style="font-weight: bold;">where</span> id =  3

<span style="font-weight: bold;">update</span> post_comment <span style="font-weight: bold;">set</span> post_id = 1 <span style="font-weight: bold;">where</span> id =  4
</pre>
</div>
<p>
查看 <a href="https://vladmihalcea.com/hibernate-facts-knowing-flush-operations-order-matters/">原理</a>
</p>
<p>
这种方式，如果执行删除：
</p>
<div class="org-src-container">
<pre class="src src-java">post.getComments().remove(0);
</pre>
</div>
<p>
生成的SQL语句：
</p>
<div class="org-src-container">
<pre class="src src-sql"><span style="font-weight: bold;">update</span> post_comment <span style="font-weight: bold;">set</span> post_id = <span style="font-weight: bold;">null</span> <span style="font-weight: bold;">where</span> post_id = 1 <span style="font-weight: bold;">and</span> id = 2

<span style="font-weight: bold;">delete</span> <span style="font-weight: bold;">from</span> post_comment <span style="font-weight: bold;">where</span> id=2
</pre>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-org11d7ddd">
<h2 id="org11d7ddd">双向关联</h2>
<div class="outline-text-2" id="text-org11d7ddd">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@Entity</span>(name = <span style="font-style: italic;">"Post"</span>)
<span style="font-weight: bold; text-decoration: underline;">@Table</span>(name = <span style="font-style: italic;">"post"</span>)
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">Post</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Id</span>
    <span style="font-weight: bold; text-decoration: underline;">@GeneratedValue</span>
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">Long</span> <span style="font-weight: bold; font-style: italic;">id</span>;

    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">String</span> <span style="font-weight: bold; font-style: italic;">title</span>;

    <span style="font-weight: bold; text-decoration: underline;">@OneToMany</span>(
        mappedBy = <span style="font-style: italic;">"post"</span>,
        cascade = <span style="font-weight: bold; text-decoration: underline;">CascadeType</span>.ALL,
        orphanRemoval = <span style="font-weight: bold; text-decoration: underline;">true</span>
    )
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">List</span>&lt;<span style="font-weight: bold; text-decoration: underline;">PostComment</span>&gt; <span style="font-weight: bold; font-style: italic;">comments</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">ArrayList</span>&lt;&gt;();

    <span style="font-weight: bold; font-style: italic;">//</span><span style="font-weight: bold; font-style: italic;">Constructors, getters and setters removed for brevity</span>

    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">addComment, removeComment这几个方法，用于同步两个实体的关联。这样的方法每个这样双向关联的类都应该提供</span>

    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">addComment</span>(<span style="font-weight: bold; text-decoration: underline;">PostComment</span> <span style="font-weight: bold; font-style: italic;">comment</span>) {
        comments.add(comment);
        comment.setPost(<span style="font-weight: bold;">this</span>);
    }

    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">removeComment</span>(<span style="font-weight: bold; text-decoration: underline;">PostComment</span> <span style="font-weight: bold; font-style: italic;">comment</span>) {
        comments.remove(comment);
        comment.setPost(<span style="font-weight: bold; text-decoration: underline;">null</span>);
    }
}

<span style="font-weight: bold; text-decoration: underline;">@Entity</span>(name = <span style="font-style: italic;">"PostComment"</span>)
<span style="font-weight: bold; text-decoration: underline;">@Table</span>(name = <span style="font-style: italic;">"post_comment"</span>)
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">PostComment</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Id</span>
    <span style="font-weight: bold; text-decoration: underline;">@GeneratedValue</span>
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">Long</span> <span style="font-weight: bold; font-style: italic;">id</span>;

    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">String</span> <span style="font-weight: bold; font-style: italic;">review</span>;

    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">注意FetchType，FetchType.EAGER在每次查询PostComment实体时都会join Post表，性能不好</span>
    <span style="font-weight: bold; text-decoration: underline;">@ManyToOne</span>(fetch = <span style="font-weight: bold; text-decoration: underline;">FetchType</span>.LAZY)
    <span style="font-weight: bold;">private</span> <span style="font-weight: bold; text-decoration: underline;">Post</span> <span style="font-weight: bold; font-style: italic;">post</span>;

    <span style="font-weight: bold; font-style: italic;">//</span><span style="font-weight: bold; font-style: italic;">Constructors, getters and setters removed for brevity</span>

    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">重写的equals方法，通过id进行比较是否相同</span>
    <span style="font-weight: bold; text-decoration: underline;">@Override</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">boolean</span> <span style="font-weight: bold;">equals</span>(<span style="font-weight: bold; text-decoration: underline;">Object</span> <span style="font-weight: bold; font-style: italic;">o</span>) {
        <span style="font-weight: bold;">if</span> (<span style="font-weight: bold;">this</span> == o) <span style="font-weight: bold;">return</span> <span style="font-weight: bold; text-decoration: underline;">true</span>;
        <span style="font-weight: bold;">if</span> (!(o <span style="font-weight: bold;">instanceof</span> PostComment )) <span style="font-weight: bold;">return</span> <span style="font-weight: bold; text-decoration: underline;">false</span>;
        <span style="font-weight: bold;">return</span> id != <span style="font-weight: bold; text-decoration: underline;">null</span> &amp;&amp; id.equals(((<span style="font-weight: bold; text-decoration: underline;">PostComment</span>) o).getId());
    }

    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">重写的hashCode方法，</span>
    <span style="font-weight: bold; text-decoration: underline;">@Override</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">int</span> <span style="font-weight: bold;">hashCode</span>() {
        <span style="font-weight: bold;">return</span> getClass().hashCode();
    }
}
</pre>
</div>
<p>
注意：
</p>
<ul class="org-ul">
<li>在每个Post这样双向关联的实体中都应该提供addComment，removeComment方法，否则会遇到 <a href="https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/">very subtle state propagation issues</a> 问题</li>
<li><a href="https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/">为什么要重写equals hashCode</a></li>
</ul>
<p>
双向关联的实体，JPA生成的语句很正常没有上面那些额外的update
</p>
</div>
</div>
<div class="outline-2" id="outline-container-org86d279e">
<h2 id="org86d279e">ManyToOne 单向关联</h2>
<div class="outline-text-2" id="text-org86d279e">
<p>
仅在 PostComment 类上加 @ManyToOne 也是允许的，甚至很多场景只需要这个单向关联即满足需求。
</p>
<p>
查询某个Post的所有PostComment只需要下面的语句：
</p>
<div class="org-src-container">
<pre class="src src-jpql">select pc from PostComment pc where pc.post.id = :postId
</pre>
</div>
</div>
</div>
</div>
<div class="status" id="postamble">
<p class="date">Date: 2020-12-29</p>
<p class="author">Author: gdme1320</p>
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
</div>
